//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: StorageBase.cs
//
//  Description: Provides a base class for storages.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using Chili.Opf3.Query;
using System.Data;

namespace Chili.Opf3.Storages
{
	/// <summary>
	/// Abstract base class that implements the functionality to access storages.
	/// </summary>
	/// <remarks>
	/// This class implements a abstract base class to access any kind of storage. It
	/// provides the main functionality and some methods that have to be overriden in derived classes.
	/// </remarks>
	/// <example>
	/// For examples how to implement your own storage see: MsSqlStorage, OracleStorage or any other 
	/// storage that comes with the framework.
	/// </example>
	public abstract class StorageBase : MarshalByRefObject, IStorage
	{
		private readonly DateTime _minDateTime = new DateTime(1782, 5, 31);
		private StorageCommandBuilderCollection _commandBuilders;

		/// <summary>
		/// Creates a new instance of the <see cref="StorageBase">StorageBase Class</see>.
		/// </summary>
		public StorageBase()
		{
			_commandBuilders = new StorageCommandBuilderCollection(this);
		}

		#region IStorage Members

		/// <summary>Executes a command on the storage that does not expect any results.</summary>
		/// <remarks>
		/// If the method returns an argument it is used by the framework to set the
		///      <see cref="Opf3.FieldAttribute.AutoNumber">AutoNumber</see> property of the
		///      persistent object.
		/// </remarks>
		/// <returns>An object that represents the value of the %field or null.</returns>
		/// <param name="query">
		/// 	<see cref="Opf3.Query.IQuery">IQuery</see> that is compiled and executed on the
		/// storage.
		/// </param>
		public abstract object ExecuteCommand(IQuery query);

		/// <summary>
		/// Executes the <see cref="Opf3.Query.IQuery">IQuery</see> and returns an 
		/// <see cref="Opf3.ObjectReader{T}">ObjectReader</see> with the results of the query.
		/// </summary>
		/// <param name="context">
		/// 	<see cref="Opf3.ObjectContext">ObjectContext</see> connected with the
		/// <see cref="Opf3.ObjectReader{T}">ObjectReader</see>.
		/// </param>
		/// <param name="query">
		/// 	<see cref="Opf3.Query.IQuery">IQuery</see> that is compiled and executed on the
		/// storage.
		/// </param>
		/// <exception cref="ArgumentNullException">Query is null or context is null.</exception>
		public abstract ObjectReader<T> ExecuteReader<T>(ObjectContext context, IQuery query);

		/// <summary>
		/// 	<para><b>Attention:</b> This method is not intended to be used in your code.</para>
		/// 	<para>Handles the DBNull value for the given type.</para>
		/// </summary>
		/// <remarks>
		/// If the framework gets a DBNull value from the storage while populating a
		/// persistent object it calls this method to let the storage decide what do to with value.
		/// The storage has to convert the value in an appropriate way.
		/// </remarks>
		/// <param name="value">Value that should be handled.</param>
		/// <param name="type">Type of the value that is handled.</param>
		/// <exception cref="Opf3.InvalidTypeException">The type of the value is not valid for DBNull handling. 
		/// It is not supported by the framework.</exception>
		public virtual object ResolveStorageNullValue(object value, Type type)
		{
			// If the value is null return null. Nullables are also "null" here.
			if (value == null)
				return null;

			if (value is DBNull && !type.IsSubclassOf(typeof(ValueType)))
				return null;

			// Inform the developer that a struct which hasn't been modelled as Nullable
			// is null in the storage.
			Tracing.Current.TraceVerbose("Type is null in database but not nullable in the application: " + type.ToString());

			// Get the underlying type.
			Type originalType = type.UnderlyingSystemType;

			if (originalType == typeof(short)) return (short)0;
			else if (originalType == typeof(int)) return (int)0;
			else if (originalType == typeof(long)) return (long)0;
			else if (originalType == typeof(byte)) return (byte)0;
			else if (originalType == typeof(Single)) return (Single)0;
			else if (originalType == typeof(decimal)) return (decimal)0;
			else if (originalType == typeof(double)) return (double)0;
			else if (originalType == typeof(string)) return string.Empty;
			else if (originalType == typeof(bool)) return false;
			// Set the minimum date defined in OPF.Main.
			else if (originalType == typeof(DateTime)) return _minDateTime;
			else if (originalType == typeof(byte[]) || originalType == typeof(object)) return new byte[] { };

			throw new InvalidTypeException("Unsupported type encountered while converting from DBNull.");
		}

		/// <summary>
		/// Returns the list of registered classes that implement the 
		/// <see cref="IStorageCommandBuilder">IStorageCommandBuilder</see> interfaces. Those classes
		/// provide the fuctionality to convert storage independent queries to storage dependent queries.
		/// </summary>
		public virtual StorageCommandBuilderCollection StorageCommandBuilders
		{
			get { return _commandBuilders; }
		}
		
		#endregion

		/// <summary>
		/// Creates a new instance of the <see cref="Opf3.ObjectReader{T}">ObjectReader Class</see>.
		/// </summary>
		/// <param name="context">An instance of the <see cref="ObjectContext">ObjectContext</see> that is passed to the storage.</param>
		/// <param name="innerReader">The IDataReader returned by the storage (after executing the command on the storage).</param>
		/// <returns></returns>
		protected ObjectReader<T> CreateObjectReader<T>(ObjectContext context, IDataReader innerReader)
		{
			return new StorageObjectReader<T>(context, innerReader);
		}
	}
}
